﻿#region

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using DirectShowLib;
using DirectShowLib.SBE;

#endregion

namespace MediaSage.Lib.Plugin
{
    /// <summary>
    ///     Metadata editor for DVR-MS files.
    /// </summary>
    public sealed class DvrmsMetadataEditor : MetadataEditor
    {
        #region Fields

        private IStreamBufferRecordingAttribute _editor;

        #endregion Fields

        #region Constructors

        /// <summary>
        ///     Initializes the editor.
        /// </summary>
        /// <param name="filepath"> The path to the file. </param>
        public DvrmsMetadataEditor(string filepath)
            : base(filepath)
        {
            _editor = (IStreamBufferRecordingAttribute)new StreamBufferRecordingAttributes();

            //ClassId.CoCreateInstance(ClassId.RecordingAttributes);
            var sourceFilter = (IFileSourceFilter)_editor;
            sourceFilter.Load(filepath, null);
        }

        public DvrmsMetadataEditor(object editor)
            : base("blank")
        {
            _editor = (IStreamBufferRecordingAttribute)editor;
        }

        #endregion Constructors

        #region Methods

        ///// <summary>Gets all of the attributes on a file.</summary>
        /// <returns> A collection of the attributes from the file. </returns>
        public override IDictionary GetAttributes()
        {
            return GetAttributes(false);
        }

        /// <summary>
        ///     Gets all of the attributes on a file.
        /// </summary>
        /// <returns> A collection of the attributes from the file. </returns>
        public override IDictionary GetAttributes(bool forCopy)
        {
            if (_editor == null) throw new ObjectDisposedException(GetType().Name);

            var propsRetrieved = new Hashtable();
            var tagsForCopy = new List<string>();

            if (forCopy)
            {
                string overrideFile =
                    System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
                                           "MetatagOverride.txt");

                if (File.Exists(overrideFile))
                {
                    using (StreamReader sr = File.OpenText(overrideFile))
                    {
                        string tag;
                        tag = sr.ReadLine();
                        while (!string.IsNullOrEmpty(tag))
                        {
                            tagsForCopy.Add(tag);
                            tag = sr.ReadLine();
                        }
                    }
                }
                else
                {
                    tagsForCopy.AddRange(new[]
                                             {
                                                 "Description", @"WM/ParentalRating", @"WM/Provider", @"WM/MediaCredits"
                                                 ,
                                                 @"WM/MediaIsDelay", @"WM/WMRVServiceID", @"WM/WMRVInBandRatingLevel",
                                                 @"WM/MediaOriginalRunTime", @"WM/MediaIsSAP", @"WM/MediaIsFinale",
                                                 @"WM/MediaNetworkAffiliation", @"WM/WMRVOriginalSoftPrePadding",
                                                 @"WM/WMRVOriginalSoftPostPadding", "Title", @"WM/WMRVDTVContent",
                                                 @"WM/Mood", @"WM/MediaIsSubtitled", @"WM/WMRVActualSoftPrePadding",
                                                 @"WM/MediaStationName", @"WM/ContentGroupDescription", @"WM/Language",
                                                 @"WM/ParentalRatingReason", @"WM/WMRVEndTime",
                                                 @"WM/WMRVHardPostPadding",
                                                 @"WM/VideoClosedCaptioning", @"WM/WMRVInBandRatingAttributes",
                                                 @"WM/WMRVContentProtectedPercent", @"WM/MediaIsTape",
                                                 @"WM/WMRVEncodeTime"
                                                 , @"WM/MediaIsRepeat", @"WM/WMRVHDContent", @"WM/SubTitle",
                                                 @"WM/MediaIsLive", @"WM/MediaOriginalBroadcastDateTime",
                                                 @"WM/SubTitleDescription", "Author", @"WM/WMRVATSCContent",
                                                 @"WM/MediaStationCallSign", @"WM/WMRVWatched",
                                                 @"WM/WMRVInBandRatingSystem", @"WM/MediaOriginalChannel",
                                                 @"WM/AlbumTitle"
                                                 , @"WM/ProviderRating", @"WM/ProviderCopyright", @"WM/MediaIsPremiere",
                                                 @"WM/WMRVContentProtected", @"WM/Genre", @"WM/Composer",
                                                 @"WM/OriginalReleaseTime", @"WM/WMRVHardPrePadding",
                                                 @"WM/WMRVActualSoftPostPadding", @"WM/ToolName", @"WM/ToolVersion",
                                                 @"WM/WMRVScheduleItemID", @"WM/WMRVRequestID", @"WM/WMRVServiceID",
                                                 @"WM/WMRVProgramID"
                                             });

                    //tagsForCopy.AddRange(new string[] { "WM/Picture", "HasAttachedImages", "HasFileTransferStream", "HasVideo", "HasArbitraryDataStream", "Signature_Name", "WMFSDKVersion", "WM/ContainerFormat", "HasScript", "Can_Skip_Forward", "Seekable", "FileSize", "Is_Trusted", "IsVBR", "HasImage", "Broadcast", "Duration", "HasAudio", "Stridable", "VBR Peak", "WMFSDKNeeded", "Can_Skip_Backward", "Is_Protected", "OptimalBitrate", "CurrentBitrate", "Bitrate", "WM/WMRVInBandRatingLevel", "WM/WMRVInBandRatingSystem", "WM/SubTitle" });
                }

                foreach (string tag in tagsForCopy)
                {
                    StreamBufferAttrDataType attributeType;
                    byte[] attributeValue = null;
                    IntPtr attPtr = IntPtr.Zero;

                    //ushort attributeNameLength = 0;
                    ushort attributeValueLength = 0;

                    try
                    {
                        // Get the lengths of the name and the value, then use them to create buffers to receive them
                        _editor.GetAttributeByName(tag, 0, out attributeType, attPtr, ref attributeValueLength);

                        //attributeValue = new byte[attributeValueLength];
                        attPtr = Marshal.AllocHGlobal(attributeValueLength);

                        // Get the name and value
                        _editor.GetAttributeByName(tag, 0, out attributeType, attPtr, ref attributeValueLength);

                        attributeValue = new byte[attributeValueLength];
                        Marshal.Copy(attPtr, attributeValue, 0, attributeValueLength);

                        // If we got a name, parse the value and add the metadata item
                        if (attributeValue != null && attributeValue.Length > 0)
                        {
                            object val = ParseAttributeValue(attributeType, attributeValue);
                            string key = tag;
                            propsRetrieved[key] = new MetadataItem(key, val, attributeType);
                        }
                    }
                    finally
                    {
                        if (attPtr != IntPtr.Zero)
                            Marshal.FreeHGlobal(attPtr);
                    }
                }

                if (!propsRetrieved.ContainsKey(@"WM/WMRVContentProtected"))
                    propsRetrieved.Add(@"WM/WMRVContentProtected",
                                       new MetadataItem(@"WM/WMRVContentProtected", false, StreamBufferAttrDataType.Bool));
            }
            else
            {
                // Get the number of attributes
                ushort attributeCount = 0;
                _editor.GetAttributeCount(0, out attributeCount);

                propsRetrieved.Add("FileName", new MetadataItem("FileName", _path, StreamBufferAttrDataType.String));

                // Get each attribute by index
                for (ushort i = 0; i < attributeCount; i++)
                {
                    IntPtr attPtr = IntPtr.Zero;
                    StreamBufferAttrDataType attributeType;
                    StringBuilder attributeName = null;
                    byte[] attributeValue = null;
                    ushort attributeNameLength = 0;
                    ushort attributeValueLength = 0;

                    try
                    {
                        // Get the lengths of the name and the value, then use them to create buffers to receive them
                        //uint reserved = 0;
                        _editor.GetAttributeByIndex(i, 0, attributeName, ref attributeNameLength,
                                                    out attributeType, attPtr, ref attributeValueLength);

                        attPtr = Marshal.AllocHGlobal(attributeValueLength);

                        attributeName = new StringBuilder(attributeNameLength);
                        attributeValue = new byte[attributeValueLength];

                        // Get the name and value
                        _editor.GetAttributeByIndex(i, 0, attributeName, ref attributeNameLength,
                                                    out attributeType, attPtr, ref attributeValueLength);

                        Marshal.Copy(attPtr, attributeValue, 0, attributeValueLength);

                        // If we got a name, parse the value and add the metadata item
                        if (attributeName != null && attributeName.Length > 0)
                        {
                            object val = ParseAttributeValue(attributeType, attributeValue);
                            string key = attributeName.ToString().TrimEnd('\0');

                            //if (!tagsForCopy.Contains(key))
                            propsRetrieved[key] = new MetadataItem(key, val, attributeType);
                        }
                    }
                    catch (Exception)
                    {
                    }
                    finally
                    {
                        if (attPtr != IntPtr.Zero)
                            Marshal.FreeHGlobal(attPtr);
                    }
                }
            }

            // Return the parsed items
            return propsRetrieved;
        }

        public MetaDataPicture GetWMPicture()
        {
            if (_editor == null) throw new ObjectDisposedException(GetType().Name);

            var picture = new WMPicture();
            IntPtr pictureParam = IntPtr.Zero;
            StreamBufferAttrDataType attributeType;

            //byte[] attributeValue = null;
            ushort attributeValueLength = 0;

            try
            {
                _editor.GetAttributeByName("WM/Picture", 0, out attributeType, pictureParam, ref attributeValueLength);

                if (attributeValueLength != 0)
                {
                    //attributeValue = new byte[attributeValueLength];
                    pictureParam = Marshal.AllocHGlobal(attributeValueLength);

                    _editor.GetAttributeByName("WM/Picture", 0, out attributeType, pictureParam,
                                               ref attributeValueLength);

                    //pictureParam = Marshal.AllocHGlobal(attributeValueLength);
                    //Marshal.Copy(attributeValue, 0, pictureParam, attributeValueLength);
                    picture = (WMPicture)Marshal.PtrToStructure(pictureParam, typeof(WMPicture));

                    var wmPic = new byte[picture.dwDataLen];
                    Marshal.Copy(picture.pbData, wmPic, 0, picture.dwDataLen);
                    string description = Marshal.PtrToStringUni(picture.pwszDescription);
                    string mimeType = Marshal.PtrToStringUni(picture.pwszMIMEType);

                    var imageConverter = new ImageConverter();
                    var iPic = (Image)imageConverter.ConvertFrom(wmPic);

                    return new MetaDataPicture(iPic, (WMPictureType)picture.bPictureType, description);
                }
                else
                {
                    return null;
                }
            }
            catch (COMException ex)
            {
                int HR = Marshal.GetHRForException(ex);
                Marshal.ThrowExceptionForHR(HR);
                return null;
            }
            finally
            {
                if (pictureParam != IntPtr.Zero)
                    Marshal.FreeHGlobal(pictureParam);
                pictureParam = IntPtr.Zero;
            }
        }

        public override void SetAttributes(IDictionary propsToSet)
        {
            SetAttributes(propsToSet, true);
        }

        /// <summary>
        ///     Sets the collection of string attributes onto the specified file and stream.
        /// </summary>
        /// <param name="propsToSet"> The properties to set on the file. </param>
        public override void SetAttributes(IDictionary propsToSet, bool throwComErrors)
        {
            if (_editor == null) throw new ObjectDisposedException(GetType().Name);
            if (propsToSet == null) throw new ArgumentNullException("propsToSet");

            byte[] attributeValueBytes;

            // Add each metadata item
            foreach (DictionaryEntry entry in propsToSet)
            {
                // Get the current item and convert it as appropriate to a byte array
                var item = (MetadataItem)entry.Value;
                if (item.Name != "FileName" && TranslateAttributeToByteArray(item, out attributeValueBytes))
                {
                    //IntPtr setParm = IntPtr.Zero;

                    try
                    {
                        //setParm = Marshal.AllocHGlobal(attributeValueBytes.Length);
                        //Marshal.Copy(attributeValueBytes, 0, setParm, attributeValueBytes.Length);
                        // Set the attribute onto the file
                        _editor.SetAttribute(0, item.Name, item.Type,
                                             attributeValueBytes, Convert.ToUInt16(attributeValueBytes.Length));
                    }
                    catch (COMException exc)
                    {
                        if (throwComErrors)
                        {
                            // Try to throw a better exception if possible
                            var hr = (uint)Marshal.GetHRForException(exc);
                            switch (hr)
                            {
                                case NS_E_ATTRIBUTE_READ_ONLY:
                                    throw new ArgumentException(
                                        "An attempt was made to add, modify, or delete a read only attribute.",
                                        item.Name, exc);
                                case NS_E_ATTRIBUTE_NOT_ALLOWED:
                                    throw new ArgumentException(
                                        "An attempt was made to add attribute that is not allowed for the given media type.",
                                        item.Name, exc);
                                default:
                                    throw;
                            }
                        }
                    }
                    finally
                    {
                        //if (setParm != IntPtr.Zero)
                        //{
                        //    Marshal.FreeHGlobal(setParm);
                        //}
                    }
                }
            }
        }

        public void SetWMPicture(MetaDataPicture pic)
        {
            if (_editor == null) throw new ObjectDisposedException(GetType().Name);

            var picture = new WMPicture();
            IntPtr pictureParam = IntPtr.Zero;

            try
            {
                var imageConverter = new ImageConverter();

                ImageFormat picFormat = pic.Picture.RawFormat;

                foreach (ImageCodecInfo info in ImageCodecInfo.GetImageEncoders())
                {
                    if (info.FormatID == picFormat.Guid)
                    {
                        picture.pwszMIMEType = Marshal.StringToCoTaskMemUni(string.Format("{0}\0", info.MimeType));
                        break;
                    }
                }

                picture.pwszDescription = Marshal.StringToCoTaskMemUni(string.Format("{0}\0", pic.Description));
                picture.bPictureType = (byte)pic.PictureType;

                var data = (byte[])imageConverter.ConvertTo(pic.Picture, typeof(byte[]));
                picture.dwDataLen = data.Length;

                picture.pbData = Marshal.AllocCoTaskMem(picture.dwDataLen);

                Marshal.Copy(data, 0, picture.pbData, picture.dwDataLen);

                pictureParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(picture));
                Marshal.StructureToPtr(picture, pictureParam, false);

                var buff = new byte[Marshal.SizeOf(picture)];
                Marshal.Copy(pictureParam, buff, 0, buff.Length);

                //Hashtable hasImage = new Hashtable();
                //hasImage.Add("HasImage", new MetadataItem("HasImage", true, StreamBufferAttrDataType.Boolean));
                //SetAttributes(hasImage);

                int hr = _editor.SetAttribute(0, "WM/Picture", StreamBufferAttrDataType.Binary, buff,
                                              Convert.ToUInt16(buff.Length));
                if (hr < 0)
                {
                    Marshal.ThrowExceptionForHR(hr);
                }
            }
            catch (COMException exc)
            {
                // Try to throw a better exception if possible
                var hr = (uint)Marshal.GetHRForException(exc);
                switch (hr)
                {
                    case NS_E_ATTRIBUTE_READ_ONLY:
                        throw new ArgumentException(
                            "An attempt was made to add, modify, or delete a read only attribute.", "WM/Picture", exc);
                    case NS_E_ATTRIBUTE_NOT_ALLOWED:
                        throw new ArgumentException(
                            "An attempt was made to add attribute that is not allowed for the given media type.",
                            "WM/Picture", exc);
                    default:
                        throw;
                }
            }
            finally
            {
                if (picture.pwszDescription != IntPtr.Zero)
                    Marshal.FreeHGlobal(picture.pwszDescription);
                if (picture.pwszMIMEType != IntPtr.Zero)
                    Marshal.FreeHGlobal(picture.pwszMIMEType);
                if (picture.pbData != IntPtr.Zero)
                    Marshal.FreeHGlobal(picture.pbData);

                if (pictureParam != IntPtr.Zero)
                    Marshal.FreeHGlobal(pictureParam);
                pictureParam = IntPtr.Zero;
            }
        }

        /// <summary>
        ///     Release all resources.
        /// </summary>
        /// <param name="disposing"> Whether this is being called from IDisposable.Dispose. </param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && _editor != null)
            {
                while (Marshal.ReleaseComObject(_editor) > 0) ;
                _editor = null;
            }
        }

        #endregion Methods
    }
}